package wml.clipstore.map;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import wml.clipstore.base.Entity;
import wml.clipstore.utils.EntityUtils;

public class MapManager {

	@SuppressWarnings("rawtypes")
	private static List<Class> storableType = new ArrayList<Class>();
	private static MapManager manager;
	private Map<Class<? extends Entity>, List<Field>> storableFields = new HashMap<Class<? extends Entity>, List<Field>>();
	private Map<Class<?>, List<Field>> referringFields = new HashMap<Class<?>, List<Field>>();

	private List<String> templates;
	private List<String> rules;
	static {
		storableType.add(String.class);
		storableType.add(Number.class);
		storableType.add(Boolean.class);
		storableType.add(Character.class);

		// storableType.add(Identifier.class);
		// storableType.add(Entity.class);
		// storableType.add(Collection.class);
	}

	private MapManager() {
		loadClasses();
	}

	private void loadClasses() {
		try {
			String collection = "(deftemplate java.util.Collection (slot identifier) (multislot items) (slot class))";
			this.templates = this.toDeftemplates(Interpreter.getClasses());
			this.templates.add(collection);
			this.rules = this.buildRules(Interpreter.getClasses());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private List<String> buildRules(List<Class<? extends Entity>> classes) {
		List<String> rules = new ArrayList<String>();
		for (Class<? extends Entity> clazz : classes) {
			String cname = clazz.getName();
			List<Field> fields = this.getReferringFields(clazz);
			for (Field field : fields) {
				String fname = field.getName();
				String fType = (EntityUtils.isSubClass(field.getType(),
						Entity.class)) ? field.getType().getName()
						: "java.util.Collection";
				String rule = "(defrule replace_" + cname + "_" + fname + " "
						+ "\n\t?i<-(" + cname + "(rCount ?c&:(> ?c 0)) ("
						+ fname + " ?v)) " + "\n\t?t<-(" + fType
						+ "(identifier ?v))" + "\n\t=>" + "\n\t(modify ?i("
						+ fname + " ?t)(rCount (- ?c 1)))" + ")";
				System.out.println(rule);
				rules.add(rule);
			}
		}
		return rules;
	}

	private int isStorable(Field field) {
		Class<?> type = field.getType();
		if (EntityUtils.isSubClass(type, Entity.class)
				|| EntityUtils.isSubClass(type, Collection.class))
			return 1;
		if (type.isPrimitive())
			return 2;
		for (Class<?> clazz : storableType) {
			if (EntityUtils.isSubClass(type, clazz))
				return 3;
		}
		return 0;
	}

	public static MapManager getInstance() {
		if (manager == null)
			manager = new MapManager();
		return manager;
	}

	public List<String> toDeftemplates(Class<? extends Entity>... classes) {
		return this.toDeftemplates(classes);
	}

	public List<String> toDeftemplates(List<Class<? extends Entity>> classes) {
		List<String> templates = new ArrayList<String>();
		for (Class<? extends Entity> clazz : classes) {
			String template = this.toDeftemplate(clazz);
			templates.add(template);
		}
		return templates;
	}

	public String toDeftemplate(Class<? extends Entity> clazz) {
		List<Field> storableFields = new ArrayList<Field>();
		List<Field> referringFields = new ArrayList<Field>();
		String deftemplate = "(deftemplate " + clazz.getName()
				+ " (slot identifier)";
		List<Field> fields = EntityUtils.getFields(clazz);
		for (Field f : fields) {
			if (this.isStorable(f) > 1) {
				storableFields.add(f);
				deftemplate += " (slot " + f.getName() + ")";
			} else if (this.isStorable(f) == 1) {
				referringFields.add(f);
				storableFields.add(f);
				deftemplate += " (slot " + f.getName() + ")";
			}
		}
		this.referringFields.put(clazz, referringFields);
		this.storableFields.put(clazz, storableFields);
		return deftemplate + " (slot class) (slot nRetain(default "
				+ referringFields.size() + "))(slot rCount(default "
				+ referringFields.size() + ")))";
	}

	public List<String> getTemplates() {
		return this.templates;
	}

	public List<String> getRules() {
		return this.rules;
	}

	public Set<Class<? extends Entity>> getStoredEntities() {
		return this.storableFields.keySet();
	}

	public List<Field> getStoredFields(Class<? extends Entity> clazz) {
		return this.storableFields.get(clazz);
	}

	public List<Field> getReferringFields(Class<?> clazz) {
		return this.referringFields.get(clazz);
	}
}
